#!/usr/bin/env python3  /*设定脚本文件直接能执行*/
# -*- coding:utf-8 -*-   /*设置编码*/
"""
@Author: zhuo
@Software: PyCharm
@File: tModbusHelper.py
@Time: 2022/11/28 16:18
@Function： Modbus通信通用模块 & 串口通信通用模块
"""

import time
import serial
import socket
import inspect
import ctypes
import threading
import serial.tools.list_ports
import binascii
from PySide6.QtCore import QObject, QTimer, Signal
from tDAL import tCrcCheck


class RwSignal(QObject):
    # 自定义信号传递的数据类型object
    receive = Signal(object)
    write = Signal(object)
    # 串口Read信号
    read_start = Signal()
    read_end = Signal()
    # 串口Write信号
    write_start = Signal()
    write_end = Signal()
    # 串口异常信号
    error_signal = Signal(object)


class ctModbusRTUHelper:
    # 初始化
    def __init__(self):
        super().__init__()
        # 初始化串口对象
        self.serial = serial.Serial()
        # 创建定时发送定时器
        self.timer_send = QTimer()
        # 信号类的实例化
        self.rwSignal = RwSignal()
        # 初始化串口列表
        self.com_list = []
        # 初始化串口read线程
        self.__m_thread_read = None
        # 循环读取串口缓存区线程标识位
        self.__m_readThreadFlag = False
        # 单独接收信号标识位
        self.__m_isSingleReadFlag = False
        # 线程锁 初始化
        self.__m_Lock = None
        # 线程锁
        self.__m_Lock = threading.Lock()
        # 定义从串口缓存区得到的字节数组
        self.__m_receiveData = bytes()

    # 析构函数
    def __del__(self):
        ir = 0
        # 判断串口类实例是否存在
        if self.serial:
            # 关闭串口
            self.serial.close()
            # 删除串口类实例
            del self.serial
        return ir

    # 串口刷新函数
    def f_refreshCom(self):
        """
        function:  串口刷新函数，执行该方法可以返回串口的数量，并且更新实例属性
              in:  None
             out:  ir：返回串口数量;
                   实例属性self.com_list：串口名称列表
          return:  int >0 ok, <0 somewrong
          others:  refreshPort
        """
        ir = 0
        # 刷新时要先清空，不然会一直添加
        self.com_list.clear()
        # 获取本机串口
        port_list = list(serial.tools.list_ports.comports())
        # 如果存在串口
        if len(port_list) > 0:
            for port_com in port_list:
                # COM5 - USB-SERIAL CH340 (COM5)
                port_serial = list(port_com)[0]
                # 循环将串口添加到self.com_list中
                self.com_list.append(port_serial)
                # 返回串口数量
                ir += 1
            return ir
        # 如果不存在串口
        else:
            # 返回-1
            ir = -1
            return ir

    # 串口打开函数
    def f_openCom(self, port, baudrate=9600, bytesize="8", stopbits="1", parity="N"):
        """
        function:  串口打开函数
              in:  port：串口名
                   baud：波特率
                   data：数据位
                   stop：停止位
                   parity：校验位
             out:  None
          return:  true：opened ; false，closed
          others:  open serial function
        """
        # 如果串口已经打开
        if self.serial.isOpen():
            # 关闭计时器
            self.timer_send.stop()
            # 关闭串口
            self.f_closeCom()
            # 返回False
            return self.serial.isOpen()
        # 如果串口未打开
        else:
            # 配置串口
            self.__f_initCOM(port, baudrate, bytesize, stopbits, parity)
            # 设置读超时时间为0.5s
            self.serial.timeout = 0.5
            # 打开串口
            self.serial.open()
            # 增加对波特率的处理
            if baudrate == 9600:
                time_read_interval = 0.1
            elif baudrate == 4800:
                time_read_interval = 0.2
            elif baudrate == 2400:
                time_read_interval = 0.4
            elif baudrate == 1200:
                time_read_interval = 0.8
            else:
                time_read_interval = 0.1
            # 定义串口读取线程
            self.__m_thread_read = threading.Thread(target=self.f_comRead, args=(time_read_interval,))
            # 设置该线程为守护线程，表示该线程是不重要的,进程退出时不需要等待这个线程执行完成。
            self.__m_thread_read.setDaemon(True)
            # 循环读取串口缓存区线程标识位置为True
            self.__m_readThreadFlag = True
            # 线程开启
            self.__m_thread_read.start()
            # 返回True
            return self.serial.isOpen()

    # 串口关闭函数
    def f_closeCom(self):
        """
        function:  串口关闭函数
              in:  None
             out:  None
          return:  int =1 ok, =0 serial is closed
          others:  Serial port close function
        """
        self.__m_Lock.acquire()
        ir = self.__f_CloseCom()
        if ir == 1:
            self.__m_Lock.release()
        return ir

    # 判断串口实例是否存在和打开
    def f_readComIsOpen(self):
        """
        function:  判断串口实例是否存在和打开函数
              in:  None
             out:  None
          return: ir=-1：串口实例不存在
                  ir= 0：串口实例存在且关闭
                  ir= 1：串口实例存在
                  ir= 2：串口实例存在且打开
          others:  Determines whether the serial port instance exists and opens functions
        """
        ir = 0
        # 判断串口实例是否存在
        if self.serial is not None:
            # 存在 ir=1
            ir += 1
            # 判断串口实例是否打开
            if self.serial.isOpen():
                # 打开，返回2
                ir += 1
            # 关闭
            else:
                # 返回0
                ir -= 1
        # 串口实例不存在
        else:
            # 返回-1
            ir -= 1
        return ir

    # 串口发送函数
    def f_comSend(self, send_text):
        """
        function:  串口发送函数
              in:  sendText：要发送的内容
                   isDisplayTime：显示内容是否要显示时间
                   isHex：显示内容是否是十六进制
             out:  发送数据的字符长度
          return:  int >0 ok, ir = 0 send is null
          others:  Serial port close function
        """
        # 判断输入的字符串是否为空
        if send_text == "":
            ir = 0
            return ir
        else:
            if self.serial.isOpen():
                try:
                    # self.rwSignal.write_start.emit()
                    # 发送
                    self.serial.write(send_text)
                    ir = len(send_text)
                    # 发送信号,每发射一次,连接槽函数就执行一次
                    self.rwSignal.write.emit(send_text)
                    # self.rwSignal.write_end.emit()
                except Exception as e:
                    self.rwSignal.error_signal.emit(f"串口发送异常：{e}")
                    ir = -1
            else:
                ir = 0
        return ir

    # 串口接受函数
    def f_comRead(self, receive_time):
        """
        function:  串口接收函数
              in:  receive_time：接收间隔时间
             out:  self.data：串口缓存区接收到的字节数组
          return:  int >0 ok, ir < 0 someWrong
          others:  Serial port Read function
        """
        ir = 0
        while self.serial.isOpen() and self.__m_readThreadFlag:
            # 每100ms 读取一次   sleep() 与 inWaiting() 最好配对使用
            time.sleep(receive_time)
            try:
                # in_waiting：返回接收缓存中的字节数。
                num = self.serial.inWaiting()
                if num and num > 0:
                    # read()：从端口读字节数据 read()读取的数据为byte型
                    self.__m_receiveData = self.serial.read(num)
                    # 等待所有数据写出
                    self.serial.flush()
                    # 如果Modbus单独接收信号标识位为False，发送信号
                    if not self.__m_isSingleReadFlag:
                        # 收到数据发送信号，更新串口界面UI
                        self.rwSignal.receive.emit(self.__m_receiveData)
                    ir += 1
            except Exception as e:
                self.rwSignal.error_signal.emit(f"串口读取异常：{e}")
                ir = -1
        else:
            ir = -2
        return ir

    # 串口发送定时器 启动&停止函数
    def f_comSendTimer(self, is_send, send_time="1000"):
        """
               function:  串口发送定时器 启动&停止函数
                     in:  is_send：是否定时发送
                          send_time：定时时间，默认1000ms
                    out:  None
                 return:  None
                 others:  Serial port send timer start & stop function
               """
        if is_send:
            time_val = int(send_time)
            if time_val > 0:
                # 启动定时器
                self.timer_send.start(time_val)
        else:
            # 定时器停止
            self.timer_send.stop()

            # 私有方法 ：串口配置函数

    # <editor-fold desc="私有方法">
    def __f_initCOM(self, port, baudrate, databits, stopbits, parity):
        """
        function:  串口配置函数
              in:  port：串口名
                   baud：波特率
                   data：数据位
                   stop：停止位
                   parity：校验位
                   timeout：读超时设置
                   write_timeout:写超时
             out:  None
          return:  None
          others:  init serial function
        """
        # 串口名称去空格大写
        port = port.strip().upper()
        # 判断串口名称是否为空
        if port != "":
            self.serial.port = port
        # 判断波特率是否大于0
        if int(baudrate) > 0:
            self.serial.baudrate = int(baudrate)
        # 判断数据位是否大于0
        if int(databits) > 0:
            self.__f_dataBitsHandle(databits)
        # 判断停止位是否大于0
        if int(stopbits) > 0:
            # 停止位处理
            self.__f_stopBitsHandle(stopbits)
        # 校验位处理
        self.__f_parityHandle(parity)

        # 私有方法 ：关闭串口操作

    def __f_CloseCom(self):
        ir = 0
        # 判断串口是否存在
        if not self.serial:
            # 不存在 直接返回0
            return ir
        if self.f_readComIsOpen() == 2:
            # 清除输入缓冲区数据
            self.serial.flushInput()
            # 清除输出缓冲区数据并终止输出
            self.serial.flushOutput()
            # 循环读取串口缓存区标识位置为False
            self.__m_readThreadFlag = False
            # 停止接收线程 这个地方做双重保险，关闭接收线程
            self.__f_stopThread(self.__m_thread_read)
            # 关闭串口
            self.serial.close()
        # 成功关闭 返回1
        ir = 1
        return ir

        # 私有方法 ：数据位输入字符处理函数

    def __f_dataBitsHandle(self, databits):
        """
        function:  数据位输入字符处理函数
              in:  databits：输入的数据位字符串
             out:  None
          return:  ir=5：数据位5
                   ir=6：数据位6
                   ir=7：数据位7
                   ir=8：数据位8
                   ir=-1：输入值错误
          others:  Check  input character handler
        """
        # 数据位5
        if databits == '5':
            self.serial.bytesize = serial.FIVEBITS
            ir = 5
        # 数据位6
        elif databits == '6':
            self.serial.bytesize = serial.SIXBITS
            ir = 6
        # 数据位7
        elif databits == '7':
            self.serial.databits = serial.SEVENBITS
            ir = 7
        # 数据位8
        elif databits == '8':
            self.serial.databits = serial.EIGHTBITS
            ir = 8
        else:
            ir = -1
        return ir

        # 私有方法 ：检验位输入字符处理函数

    def __f_parityHandle(self, parity):
        """
        function:  检验位输入字符处理函数
              in:  parity：输入的校验位字符串
             out:  None
          return:  ir=1：无校验
                   ir=2：奇校验
                   ir=3：偶校验
                   ir=-1：输入值错误
          others:  Check  input character handler
        """
        # 无校验
        if parity == 'N':
            self.serial.parity = serial.PARITY_NONE
            ir = 1
        # 奇校验
        elif parity == 'O':
            self.serial.parity = serial.PARITY_ODD
            ir = 2
        # 偶校验
        elif parity == 'E':
            self.serial.parity = serial.PARITY_EVEN
            ir = 3
        else:
            ir = -1
        return ir

        # 私有方法 ：停止位输入字符处理函数

    def __f_stopBitsHandle(self, stopbits):
        """
        function:  停止位输入字符处理函数
              in:  stop：输入的停止位字符串
             out:  None
          return:  ir=1：停止位1
                   ir=1.5：停止位1.5
                   ir=2：停止位2
                   ir=0：输入值错误
          others:  Check  input character handler
        """
        # 停止位1
        if stopbits == '1':
            self.serial.stopbits = serial.STOPBITS_ONE
            ir = 1
        # 停止位1.5
        elif stopbits == '1.5':
            self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
            ir = 1.5
        # 停止位2
        elif stopbits == '2':
            self.serial.stopbits = serial.STOPBITS_TWO
            ir = 2
        else:
            ir = -1
        return ir

        # 线程停止

    def __f_stopThread(self, thread):
        """
        function:  线程停止
              in:  thread：需要停止的线程
             out:  None
          return:  None
          others:  Thread Stop Function
        """
        self.__f_asyncRaise(thread.ident, SystemExit)

    @staticmethod
    def __f_asyncRaise(tid, excType):
        """raises the exception, performs cleanup if needed"""
        threadId = ctypes.c_long(tid)
        if not inspect.isclass(excType):
            excType = type(excType)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(threadId, ctypes.py_object(excType))
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(threadId, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    # </editor-fold>

    # <editor-fold desc="Modbus通讯相关">
    # Modbus通讯执行函数
    def f_modbusExecute(self, dev_add, fun_code, data_add, rw_data, convert=True, num=0):
        """
        function:  Modbus通讯执行函数
              in:  devAdd：控制器地址(int类型)；
                   funCode：功能码(int类型)；
                   dataAdd：数据地址(int&string类型)；
                   rwData：读取长度或写入数据(int类型)或连续写入数据(list类型);
                   num：个数(int类型)，主要用于功能码10，连续写入多个寄存器；
                   convert：True or False，是否需要进行十进制转换；
             out:  resDic  :  结果字典{'data': {}, 'add': {}, 'resMode': {}}
                   resMode :  执行结果模式
                         1 :  写单一寄存器：收发正确
                         0 :  读保持寄存器：收发正确
                        -1 :  CRC校验错误
                        -2 :  数据长度错误
                        -3 :  帧长错误
                        -4 :  返回数据为空
                        -5 :  串口未打开
                        -6 :  写入或读取的数据乘上缩放倍数后转换成十六进制的长度错误
          return:  {'data': {}, 'add': {}, 'resMode': {}}
          others:  Modbus Communication Executive Func
        """
        # 单独接收信号标识位置为True
        self.__m_isSingleReadFlag = True
        # 判断串口是否打开
        if self.f_readComIsOpen() == 2:
            # 先清除从串口缓存区得到的字节数组
            self.__m_receiveData = bytes()

            # <editor-fold desc="控制器地址、功能码、数据地址、读取长度或写入数据">
            # 控制器地址
            dev_add = hex(int(dev_add))[2:].upper().zfill(2)

            # 功能码
            fun_code = hex(int(fun_code))[2:].upper().zfill(2)

            # 数据地址
            if type(data_add) == str:
                data_add = int(dev_add)
            data_add = f'{data_add:04X}'
            # if type(dataAdd) == int:
            #     # 如果是整型，转换位字符串类型
            #     dataAdd = str(dataAdd)
            # dataAdd = dataAdd.upper().zfill(4)

            # 读取长度或写入数据
            if type(rw_data) != list:
                rw_data = int(rw_data)
                if rw_data < 0:
                    # f_signedIntToHex首先要判断是否是负数
                    rw_data = f_signedIntToHex(rw_data, 16)
                else:
                    rw_data = hex(rw_data)[2:].upper().zfill(4)
            else:
                # 先定义一个空字符串
                new_rwData = ""
                # 如果是列表类型，则需要连续写入
                for i in range(len(rw_data)):
                    if int(rw_data[i]) < 0:
                        # f_signedIntToHex首先要判断是否是负数
                        new_rwData += f_signedIntToHex(int(rw_data[i]), 16)
                    else:
                        new_rwData += hex(int(rw_data[i]))[2:].upper().zfill(4)
                rw_data = new_rwData
            # </editor-fold>

            # <editor-fold desc="拼接报文">
            # 拼接报文：控制器地址+功能码+数据地址+写入数据或读取长度
            middleMsg = dev_add + fun_code + data_add + rw_data
            # <editor-fold desc="判断功能码是否是16">
            # 连续写入个数
            if num > 0:
                # 字节个数
                numOfBytes = num * 2
                numOfBytes = hex(int(numOfBytes))[2:].upper().zfill(2)
                # 连续写入个数
                num = hex(int(num))[2:].upper().zfill(4)
                # 拼接报文：控制器地址+功能码+数据地址+写入数据或读取长度
                middleMsg = dev_add + fun_code + data_add + num + numOfBytes + rw_data
            # </editor-fold>
            # 中间报文字符串转bytes
            msgBytes = bytes(middleMsg, encoding="utf8")
            # bytes转十六进制bytes
            msgBytes = binascii.unhexlify(msgBytes)
            # </editor-fold>

            # <editor-fold desc="CRC校验">
            # 获取校验和
            crc = tCrcCheck.f_crc16ModbusFunc(msgBytes)
            # 校验和转十六进制，大写，取Ox，补0
            crc = hex(crc).upper()[2:].zfill(4)
            # 校验和高低位互换，低位在前
            crc = crc[2:] + crc[0:2]
            # </editor-fold>

            # <editor-fold desc="完整报文拼接">
            # 完整报文
            msg = middleMsg + str(crc)
            # 字符串转为bytes格式
            sendContent = msg.encode("utf-8")
            # bytes转十六进制bytes
            sendContent = binascii.unhexlify(sendContent)
            # </editor-fold>

            # <editor-fold desc="发送数据">
            # 串口数据发送
            self.f_comSend(sendContent)
            # 发送信号,每发射一次,连接槽函数就执行一次
            # self.rwSignal.write.emit(sendContent)
            # 这个地方要加一个0.2s延迟，不然读取数据为空
            time.sleep(0.2)
            # </editor-fold>

            # <editor-fold desc="判断接收数据是否为空">
            if self.__m_receiveData != bytes():
                if self.__m_isSingleReadFlag:
                    # 收到数据发送信号，更新串口界面UI
                    self.rwSignal.receive.emit(self.__m_receiveData)
                    # CRC校验，如果正确进行下一步处理
                    if tCrcCheck.f_checkCrcFunc(self.__m_receiveData):
                        # 16进制Bytes–>Bytes
                        sendContent = binascii.hexlify(self.__m_receiveData).upper()
                        # 调用Modbus接收数据解析函数
                        resDic = self.__f_receiveMsgParse(rec_bytes=sendContent, length=rw_data, convert=convert)
                        # 单独接收信号标识位置为False
                        self.__m_isSingleReadFlag = False
                        return resDic
                    else:
                        resDic = {'data': {}, 'add': {}, 'resMode': -1}
                        # 单独接收信号标识位置为False
                        self.__m_isSingleReadFlag = False
                        return resDic
            else:
                resDic = {'data': {}, 'add': {}, 'resMode': -4}
                # 单独接收信号标识位置为False
                self.__m_isSingleReadFlag = False
                return resDic
            # </editor-fold>

        else:
            resDic = {'data': {}, 'add': {}, 'resMode': -5}
            # 单独接收信号标识位置为False
            self.__m_isSingleReadFlag = False
            return resDic

    # Modbus接收数据解析函数
    def __f_receiveMsgParse(self, rec_bytes, length, convert):
        """
        function:  Modbus接收数据解析函数
              in:  rec_bytes：十六进制bytes，串口最终发送的数据；
                   length：rwData的长度，即读取长度或写入数据的长度；
                   convert：True or False，是否需要进行十进制转换；
             out:  __m_resDic
          return:  {'data': {}, 'add': {}, 'resMode': {}}
          others:  Modbus Receives Data Parse Func
        """
        # Bytes– > 字符串
        rec_content = str(rec_bytes, encoding="utf-8")
        # 接收字节的总长度
        __m_length = int(len(rec_content) / 2)
        # 实际站地址
        __m_devAdd = rec_content[0:2]
        # 实际功能码
        __m_funCode = rec_content[2:4]
        # 初始化返回结果列表
        __m_resDic = {'data': {}, 'add': {}, 'resMode': {}}
        # 读保持寄存器:03
        if __m_funCode == "03":
            # ---Modbus读取数据开始时发送信号--- #
            self.rwSignal.read_start.emit()
            # 判断长度是否正确
            if __m_length == 5 + int(length, 16) * 2:
                __m_dataLength = rec_content[4:6]
                # 判断数据长度是否正确
                __m_dataLength = rec_content[4:6]
                data_length_hex = hex(int(length, 16) * 2)[2:].zfill(2).upper()
                # 判断数据长度是否正确
                if __m_dataLength == data_length_hex:
                    __m_data = rec_content[6:6 + int(length, 16) * 4]
                    if convert:
                        # V2.0.0 参数数据类型要做有符号转换
                        __m_data = f_hexToSignedInt(__m_data)
                    # __m_data = int(__m_data, 16)
                    __m_resDic["data"] = __m_data
                    __m_resDic["resMode"] = 0
                    # ---Modbus读取数据结束时发送信号--- #
                    time.sleep(0.05)
                    self.rwSignal.read_end.emit()
                    return __m_resDic
                else:
                    # ---Modbus读取数据结束时发送信号--- #
                    time.sleep(0.05)
                    self.rwSignal.read_end.emit()
                    __m_resDic["resMode"] = -2
                    return __m_resDic
            else:
                # ---Modbus读取数据结束时发送信号--- #
                time.sleep(0.05)
                self.rwSignal.read_end.emit()
                __m_resDic["resMode"] = -3
                return __m_resDic
        # 写单一寄存器:06 非标准扩展33:适用于工业风机产测模式启动帧(如遇其他项目可删掉)
        elif __m_funCode == "06" or __m_funCode == "33":
            self.rwSignal.write_start.emit()
            # 写入返回数据相同
            if __m_length == 8:
                __m_dataAdd = rec_content[4:8]
                __m_data = rec_content[8:12]
                __m_data = int(__m_data, 16)
                __m_resDic["add"] = __m_dataAdd
                __m_resDic["data"] = __m_data
                __m_resDic["resMode"] = 1
                time.sleep(0.05)
                self.rwSignal.write_end.emit()
                return __m_resDic
            else:
                __m_resDic["resMode"] = -3
                time.sleep(0.05)
                self.rwSignal.write_end.emit()
                return __m_resDic

        # 写连续寄存器:十进制16 十六进制10
        elif __m_funCode == "10":
            self.rwSignal.write_start.emit()
            # 返回数据长度为8
            if __m_length == 8:
                # 寄存器地址
                __m_dataAdd = rec_content[4:8]
                __m_resDic["add"] = __m_dataAdd
                # 写入个数
                __m_data = rec_content[8:12]
                __m_data = int(__m_data, 16)
                __m_resDic["data"] = __m_data
                # 结果
                __m_resDic["resMode"] = 1
                time.sleep(0.05)
                self.rwSignal.write_end.emit()
                return __m_resDic
            else:
                __m_resDic["resMode"] = -3
                time.sleep(0.05)
                self.rwSignal.write_end.emit()
                return __m_resDic
    # </editor-fold>


class ctModbusTCPHelper:
    # 初始化
    def __init__(self):
        super().__init__()
        # 初始化socket对象
        self.socket = None
        # 创建定时发送定时器
        self.timer_send = QTimer()
        # 信号类的实例化
        self.rwSignal = RwSignal()
        # 线程锁
        self.__m_Lock = threading.Lock()
        # 连接状态标识
        self.__m_isConnected = False
        # 主机地址
        self.__m_host = ""
        # 端口号
        self.__m_port = 502
        # 接收线程标识
        self.__m_receiveThreadFlag = False
        # 接收线程
        self.__m_receiveThread = None

    # 析构函数
    def __del__(self):
        # 断开连接
        self.f_disconnectTCP()
        return 0

    # TCP连接函数
    def f_connectTCP(self, host, port=502):
        """
        function:  TCP连接函数
              in:  host：主机地址
                   port：端口号，默认502
             out:  None
          return:  True：连接成功；False：连接失败
          others:  TCP connection function
        """
        try:
            import socket

            # 如果已连接，先断开
            if self.__m_isConnected:
                self.f_disconnectTCP()

            # 创建socket对象
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 设置超时时间
            self.socket.settimeout(2.0)
            # 连接服务器
            self.socket.connect((host, port))

            # 更新连接状态和主机信息
            self.__m_isConnected = True
            self.__m_host = host
            self.__m_port = port

            # 启动接收线程
            self.__m_receiveThreadFlag = True
            self.__m_receiveThread = threading.Thread(target=self.__f_tcpReceiveThread)
            self.__m_receiveThread.daemon = True
            self.__m_receiveThread.start()

            return True
        except Exception as e:
            self.rwSignal.error_signal.emit(f"TCP连接异常：{e}")
            self.__m_isConnected = False
            return False

    # TCP断开连接函数
    def f_disconnectTCP(self):
        """
        function:  TCP断开连接函数
              in:  None
             out:  None
          return:  True：断开成功；False：断开失败或未连接
          others:  TCP disconnect function
        """
        try:
            # 停止接收线程
            self.__m_receiveThreadFlag = False
            if self.__m_receiveThread and self.__m_receiveThread.is_alive():
                self.__m_receiveThread.join(timeout=1.0)

            if self.socket:
                self.socket.close()
                self.socket = None
            self.__m_isConnected = False
            return True
        except Exception as e:
            self.rwSignal.error_signal.emit(f"TCP断开连接异常：{e}")
            return False

    # TCP接收线程函数
    def __f_tcpReceiveThread(self):
        """
        function:  TCP接收线程函数，用于持续监听TCP连接的数据
              in:  None
             out:  None
          return:  None
          others:  TCP receive thread function
        """
        while self.__m_isConnected and self.__m_receiveThreadFlag:
            try:
                # 设置超时时间，以便定期检查线程标志
                self.socket.settimeout(0.5)

                # 接收数据
                data = self.socket.recv(1024)
                if data:
                    # 发射接收信号
                    self.rwSignal.receive.emit(data)
            except socket.timeout:
                # 超时是正常的，继续循环
                continue
            except Exception as e:
                if self.__m_isConnected and self.__m_receiveThreadFlag:
                    self.rwSignal.error_signal.emit(f"TCP接收异常：{e}")
                    # 尝试重连
                    time.sleep(1)
                    if self.__m_isConnected and self.__m_receiveThreadFlag:
                        try:
                            self.socket.connect((self.__m_host, self.__m_port))
                        except:
                            pass
                break

    # 判断TCP连接状态
    def f_isConnected(self):
        """
        function:  判断TCP连接状态
              in:  None
             out:  None
          return:  True：已连接；False：未连接
          others:  Check TCP connection status
        """
        return self.__m_isConnected

    # TCP发送函数
    def f_tcpSend(self, send_data):
        """
        function:  TCP发送函数
              in:  send_data：要发送的数据(bytes)
             out:  发送数据的字节长度
          return:  int >0 ok, ir = 0 send is null
          others:  TCP send function
        """
        if not send_data:
            return 0

        if self.__m_isConnected and self.socket:
            try:
                # 发送数据
                sent_len = self.socket.send(send_data)
                # 发送信号
                self.rwSignal.write.emit(send_data)
                return sent_len
            except Exception as e:
                self.rwSignal.error_signal.emit(f"TCP发送异常：{e}")
                self.__m_isConnected = False
                return -1
        else:
            return 0

    # TCP接收函数（用于请求-响应模式）
    def f_tcpReceive(self, timeout=0.5):
        """
        function:  TCP接收函数（用于请求-响应模式）
              in:  timeout：接收超时时间，默认2.0秒
             out:  接收到的数据(bytes)
          return:  接收到的数据(bytes)或None(如果出错)
          others:  TCP receive function for request-response mode
        """
        if not self.__m_isConnected or not self.socket:
            return None

        try:
            # 设置超时
            self.socket.settimeout(timeout)

            # 接收数据
            receive_data = self.socket.recv(1024)
            return receive_data
        except socket.timeout:
            self.rwSignal.error_signal.emit("TCP接收超时")
            return None
        except Exception as e:
            self.rwSignal.error_signal.emit(f"TCP接收异常：{e}")
            self.__m_isConnected = False
            return None

    # Modbus TCP通讯执行函数
    def f_modbusExecute(self, dev_add, fun_code, data_add, rw_data, convert=True, num=0):
        """
        function:  Modbus TCP通讯执行函数
              in:  dev_add：单元标识符/从站地址(int类型)；
                   fun_code：功能码(int类型)；
                   data_add：数据地址(int类型)；
                   rw_data：读取长度或写入数据(int类型)或连续写入数据(list类型)；
                   num：个数(int类型)，主要用于功能码16，连续写入多个寄存器；
                   convert：True or False，是否需要进行十进制转换；
             out:  resDic  :  结果字典{'data': {}, 'add': {}, 'resMode': {}}
                   resMode :  执行结果模式
                         1 :  写单一寄存器：收发正确
                         0 :  读保持寄存器：收发正确
                        -1 :  数据校验错误
                        -2 :  数据长度错误
                        -3 :  帧长错误
                        -4 :  返回数据为空
                        -5 :  连接未建立
                        -6 :  写入或读取的数据乘上缩放倍数后转换成十六进制的长度错误
          return:  {'data': {}, 'add': {}, 'resMode': {}}
          others:  Modbus TCP Communication Executive Func
        """
        # 判断TCP连接是否建立
        if not self.__m_isConnected:
            return {'data': {}, 'add': {}, 'resMode': -5}

        # 事务标识符(简单递增，实际应用中可能需要更复杂的处理)
        transaction_id = getattr(self, '__m_transaction_id', 0)
        transaction_id = (transaction_id + 1) % 65536
        setattr(self, '__m_transaction_id', transaction_id)

        # <editor-fold desc="构建Modbus TCP报文">
        # 控制器地址/单元标识符
        dev_add = int(dev_add)

        # 功能码
        fun_code = int(fun_code)

        # 数据地址
        data_add = int(data_add)

        # 读取长度或写入数据
        if isinstance(rw_data, list):
            # 连续写入多个寄存器
            num = len(rw_data) if num <= 0 else num
            byte_count = num * 2
            # 构建数据部分
            data_values = b''
            for value in rw_data:
                # 确保值是整数
                int_value = int(value)  # 将字符串转换为整数
                if int_value < 0:  # 使用转换后的整数值
                    # 处理有符号数
                    hex_value = f_signedIntToHex(int_value, 16)  # 使用转换后的整数值
                    data_values += binascii.unhexlify(hex_value)
                else:
                    data_values += int_value.to_bytes(2, byteorder='big')  # 使用转换后的整数值

            pdu = (fun_code).to_bytes(1, byteorder='big') + \
                  (data_add).to_bytes(2, byteorder='big') + \
                  (num).to_bytes(2, byteorder='big') + \
                  (byte_count).to_bytes(1, byteorder='big') + \
                  data_values
        else:
            # 读取或写入单个寄存器
            if fun_code in [3, 4]:  # 读保持寄存器或输入寄存器
                # 读取多个寄存器
                num_registers = int(rw_data)
                pdu = (fun_code).to_bytes(1, byteorder='big') + \
                      (data_add).to_bytes(2, byteorder='big') + \
                      (num_registers).to_bytes(2, byteorder='big')
            else:  # 写单个寄存器
                value = int(rw_data)  # 确保值是整数
                if value < 0:
                    # 处理有符号数
                    hex_value = f_signedIntToHex(value, 16)
                    value_bytes = binascii.unhexlify(hex_value)
                else:
                    value_bytes = value.to_bytes(2, byteorder='big')

                pdu = (fun_code).to_bytes(1, byteorder='big') + \
                      (data_add).to_bytes(2, byteorder='big') + \
                      value_bytes

        # 构建MBAP头
        # 事务标识符(2字节)
        # 协议标识符(2字节)，ModbusTCP固定为0
        # 长度(2字节)，单元标识符+PDU长度
        length = len(pdu) + 1  # +1 for unit identifier

        mbap_header = (transaction_id).to_bytes(2, byteorder='big') + \
                      (0).to_bytes(2, byteorder='big') + \
                      (length).to_bytes(2, byteorder='big') + \
                      (dev_add).to_bytes(1, byteorder='big')

        # 完整报文
        send_data = mbap_header + pdu
        # </editor-fold>

        # 发送数据
        send_result = self.f_tcpSend(send_data)
        if send_result <= 0:
            return {'data': {}, 'add': {}, 'resMode': -4}

        # 接收响应
        receive_data = self.f_tcpReceive()
        if not receive_data:
            return {'data': {}, 'add': {}, 'resMode': -4}

        # 发射接收信号，以便在界面上显示
        self.rwSignal.receive.emit(receive_data)

        # 解析响应
        return self.__f_parseModbusTCPResponse(receive_data, fun_code, rw_data, convert)

    # Modbus TCP响应解析函数
    def __f_parseModbusTCPResponse(self, response, fun_code, rw_data, convert):
        """
        function:  Modbus TCP响应解析函数
              in:  response：响应数据(bytes)
                   fun_code：功能码
                   rw_data：原始请求中的读写数据
                   convert：是否需要进行十进制转换
             out:  解析结果字典
          return:  {'data': {}, 'add': {}, 'resMode': {}}
          others:  Modbus TCP Response Parse Function
        """
        # 检查响应长度
        if len(response) < 9:  # MBAP头(7字节) + 功能码(1字节) + 至少1字节数据
            return {'data': {}, 'add': {}, 'resMode': -3}

        # 提取PDU部分(去掉MBAP头)
        pdu = response[7:]

        # 获取功能码
        response_fun_code = pdu[0]

        # 检查功能码是否正确
        if response_fun_code != fun_code:
            # 错误响应
            if response_fun_code == fun_code + 0x80:
                error_code = pdu[1]
                self.rwSignal.error_signal.emit(f"Modbus错误响应，错误码: {error_code}")
                return {'data': {}, 'add': {}, 'resMode': -1}
            else:
                self.rwSignal.error_signal.emit(f"功能码不匹配，期望: {fun_code}，实际: {response_fun_code}")
                return {'data': {}, 'add': {}, 'resMode': -1}

        # 根据功能码解析响应
        resDic = {'data': {}, 'add': {}, 'resMode': {}}

        # 读保持寄存器(03)或读输入寄存器(04)
        if fun_code in [3, 4]:
            self.rwSignal.read_start.emit()

            # 字节数
            byte_count = pdu[1]
            # 寄存器值
            data_bytes = pdu[2:2 + byte_count]

            # 检查数据长度
            expected_byte_count = int(rw_data) * 2
            if byte_count != expected_byte_count:
                self.rwSignal.read_end.emit()
                resDic["resMode"] = -2
                return resDic

            if convert:
                # 解析寄存器值为有符号整数
                values = []
                for i in range(0, byte_count, 2):
                    register_value = int.from_bytes(data_bytes[i:i + 2], byteorder='big')
                    if register_value >= 0x8000:  # 如果最高位为1，表示负数
                        register_value = register_value - 0x10000
                    values.append(register_value)

                # 如果只读取一个寄存器，直接返回数值
                if len(values) == 1:
                    resDic["data"] = values[0]
                else:
                    resDic["data"] = values
            else:
                # 不转换，返回十六进制字符串
                hex_str = ''
                for i in range(0, byte_count, 2):
                    register_value = int.from_bytes(data_bytes[i:i + 2], byteorder='big')
                    hex_str += f'{register_value:04X}'
                resDic["data"] = hex_str

            resDic["resMode"] = 0
            time.sleep(0.05)
            self.rwSignal.read_end.emit()

        # 写单个寄存器(06)
        elif fun_code == 6:
            self.rwSignal.write_start.emit()

            # 响应应该包含地址和值
            address = int.from_bytes(pdu[1:3], byteorder='big')
            value = int.from_bytes(pdu[3:5], byteorder='big')

            if convert and value >= 0x8000:
                value = value - 0x10000

            resDic["add"] = address
            resDic["data"] = value
            resDic["resMode"] = 1
            time.sleep(0.05)
            self.rwSignal.write_end.emit()

        # 写多个寄存器(16)
        elif fun_code == 16:
            self.rwSignal.write_start.emit()

            # 响应应该包含起始地址和寄存器数量
            address = int.from_bytes(pdu[1:3], byteorder='big')
            quantity = int.from_bytes(pdu[3:5], byteorder='big')

            resDic["add"] = address
            resDic["data"] = quantity
            resDic["resMode"] = 1
            time.sleep(0.05)
            self.rwSignal.write_end.emit()

        else:
            # 不支持的功能码
            self.rwSignal.error_signal.emit(f"不支持的功能码: {fun_code}")
            resDic["resMode"] = -1

        return resDic


# 十六进制字符串转换为有符号十进制数
def f_hexToSignedInt(hex_str, num_bits=16):
    """
    function:  十六进制字符串转换为有符号十进制数
          in:  hexStr：十六进制字符串；
               numBits：十六进制数在内存中的表示方式（例如，它是否是 16 位、32 位还是 64 位等），默认为16位；
         out:  signedInt：有符号十进制数int类型；
      return:  int
      others:  Convert Hexadecimal String To Signed Decimal Number
    """
    # 将十六进制数转换为无符号整数
    unsigned_int = int(hex_str, 16)
    # 检查最高位
    if (unsigned_int >> (num_bits - 1)) & 1:
        # 如果最高位是 1，执行二进制补码转换
        signed_int = unsigned_int - (1 << num_bits)
    else:
        # 如果最高位是 0，则无需转换
        signed_int = unsigned_int
    # 返回int类型的有符号十进制数
    return signed_int


# 有符号十进制数转换为十六进制字符串
def f_signedIntToHex(signedInt, numBits=16):
    """
    function:  有符号十进制数转换为十六进制字符串
          in:  signedInt：int类型的有符号十进制数；
               numBits：十六进制数在内存中的表示方式（例如，它是否是 16 位、32 位还是 64 位等），默认为16位；
         out:  hexStr：转换后的十六进制字符串；
      return:  string
      others:  Converts Signed Decimal Number To Hexadecimal String
    """
    # 确保输入是一个负数
    assert signedInt < 0
    # 计算补码
    mask = (1 << numBits) - 1  # 创建一个具有指定位数的全1掩码
    complement = mask + signedInt + 1  # 计算补码
    # 将补码转换为十六进制字符串
    hexStr = hex(complement)[2:].upper().zfill(numBits // 4)
    # 返回十六进制字符串
    return hexStr


if __name__ == '__main__':
    # 创建Modbus TCP助手实例
    tcp_helper = ctModbusTCPHelper()

    # 连接到Modbus TCP设备
    if tcp_helper.f_connectTCP("10.130.247.179", 8000):
        print("连接成功")

        # 读取保持寄存器（功能码03）
        result = tcp_helper.f_modbusExecute(1, 3, 0, 1)  # 从站地址1，读取地址0的1个寄存器
        if result['resMode'] == 0:
            print(f"读取到的值: {result['data']}")

        # 写入保持寄存器（功能码06）
        result = tcp_helper.f_modbusExecute(1, 6, 0, 100)  # 从站地址1，向地址0写入值100
        if result['resMode'] == 1:
            print("写入成功")

        # 断开连接
        tcp_helper.f_disconnectTCP()
    else:
        print("连接失败")
